﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using AutomationTools.Procedimentos.Exceptions;
using System.Collections;
using System.Diagnostics;
using System.ComponentModel;

namespace AutomationTools.Procedimentos.Acao.Transformacao
{
    /// <summary>
    /// Faz a conversão do texto em uma estrutura de classes de acordo com seus atributos customizados.
    /// Recebe:     string - O texto onde serão aplicadas as Regex para a conversão.
    /// Retorna:    T - Uma instância da classe.
    /// </summary>
    /// <typeparam name="T">Tipo de onde serão extraídas as Regex para a conversão na classe</typeparam>
    public class RegexParseObject<T> : IProcedimento  where T : new()
    {
        public Regex Regex { get; set; }


        /// <summary>
        /// Faz a conversão do texto em uma estrutura de classes de acordo com seus atributos customizados.
        /// </summary>
        public RegexParseObject() { }

        /// <summary>
        /// Faz a conversão do texto em uma estrutura de classes de acordo com seus atributos customizados.
        /// </summary>
        /// <param name="regex">Regex a ser executada antes do início da conversão. 
        /// Utiliza por padrão as opções RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline</param>
        public RegexParseObject(string regex)
            : this(new Regex(regex, RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline))
        {

        }

        /// <summary>
        /// Construtor padrão
        /// </summary>
        /// <param name="regex">Regex a ser executada antes do início da conversão. </param>
        public RegexParseObject(Regex regex)
        {
            Regex = regex;
        }

        public object Executar(IContexto contexto)
        {
            ValidarEntrada(contexto);

            string corpo = (string)contexto.Parametro;
            if (Regex != null)
            {
                corpo = Regex.Match(corpo).Value;
            }
            return (T)ParseObject(corpo, typeof(T), null);
        }

        private static object ParseObject(string corpo, Type tipo, Match matchPai)
        {
            object ret = Activator.CreateInstance(tipo);
            foreach (var property in tipo.GetProperties(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
            {
                var atributos = property.GetCustomAttributes(typeof(RegexParseAttribute), true);
                if(atributos.Length == 0) continue;
                var atributo = (RegexParseAttribute)atributos.GetValue(0);

                var atributosProperty = property.PropertyType.GetCustomAttributes(typeof(CanRegexParseAttribute), true);
                if (atributo.IsArray)
                {
                    var elementType = property.PropertyType.GetElementType();
                    ArrayList arr = new ArrayList();
                    foreach (Match item in atributo.Regex.Matches(corpo))
                    {
                        arr.Add(ParseObject(item.Groups[atributo.Grupo].Value, elementType, item));
                    }
                    property.SetValue(ret, arr.ToArray(elementType), null);
                }
                else
                {
                    string match;
                    if (atributo.Regex.Regex != null)
                    {
                        var matches = atributo.Regex.Matches(corpo);
                        if (matches.Count <= atributo.Index) continue;
                        match = matches[atributo.Index].Groups[atributo.Grupo].Value;
                    }
                    else if (matchPai != null)
                    {
                        match = matchPai.Groups[atributo.Grupo].Value;
                    }
                    else
                    {
                        throw new Exception(string.Format("Erro ao tentar atribuir a propriedade '{0}', a propriedade Regex do atributo não foi atribuida, e o objeto não está sendo utilizado em um array.", property.Name));
                    }

                    if (atributosProperty.Length > 0)
                    {
                        property.SetValue(ret, ParseObject(match, property.PropertyType, null), null);
                    }
                    else
                    {
                        property.SetValue(ret, TypeDescriptor.GetConverter(property.PropertyType).ConvertFrom(match), null);
                    }
                }
            }
            return ret;
        }


        public void ValidarEntrada(IContexto contexto)
        {
            if (!(contexto.Parametro is string))
                throw new ParametroException(typeof(string), contexto.Parametro.GetType());

            var atributosProperty = typeof(T).GetCustomAttributes(typeof(CanRegexParseAttribute), true);
            if (atributosProperty.Length == 0)
            {
                throw new Exception(string.Format("O tipo '{0}' não possui o atributo AutomationTools.Procedimentos.Transformacao.CanRegexParseAttribute", typeof(T).FullName));
            }
        }
    }

    public class RegexExecutor
    {
        public Regex Regex { get; set; }
        bool breakpoint = false;

        public RegexExecutor(Regex regex, bool breakpoint)
        {
            this.Regex = regex;
            this.breakpoint = breakpoint;
        }

        public MatchCollection Matches(string input)
        {
            if (breakpoint) Debugger.Break();
            return Regex.Matches(input);
        }

        public Match Match(string input)
        {
            if (breakpoint) Debugger.Break();
            return Regex.Match(input);
        }
    }
    /// <summary>
    /// Atributo que será utilizado na conversão do texto em classe.
    /// </summary>
    [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
    public sealed class RegexParseAttribute : Attribute
    {
        public RegexParseAttribute(string regex)
        {
            this._Regex = new Regex(regex, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline);
        }

        public RegexParseAttribute(int grupo)
        {
            this.Grupo = grupo;
        }

        private Regex _Regex;
        /// <summary>
        /// Regex que busca o valor que será utilizado na propriedade.
        /// </summary>
        public RegexExecutor Regex
        {
            get
            {
                return new RegexExecutor(_Regex, Debug);
            }
        }

        /// <summary>
        /// Índice do array de resultados da regex que será utilizado na propriedade.
        /// </summary>
        public int Index
        {
            get;
            set;
        }

        /// <summary>
        /// Índice do grupo do resultado da Regex que será utilizado na propriedade.
        /// </summary>
        public int Grupo { get; set; }
        
        /// <summary>
        /// Indica que o resultado da Regex será convertido em um array de objetos.
        /// </summary>
        public bool IsArray { get; set; }

        /// <summary>
        /// Define um breakpoint na hora de executar a regex.
        /// </summary>
        public bool Debug { get; set; }
    }

    /// <summary>
    /// Define um tipo que pode ser utilizado no RegexParseObject.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
    public sealed class CanRegexParseAttribute : Attribute
    {

    }
}
